Wang Haihua
🍈 🍉🍊 🍋 🍌
Keras是一个高级的深度学习API,允许你轻松地构建、训练、评估和执行各种神经网络。它的文档(或规范)可以从https://keras.io/ 获得。 这个参考实现,也称为Keras,是由Francois Chollet作为一个研究项目的一部分开发的,并在2015年3月作为一个开源项目发布。由于其易用性、灵活性和漂亮的设计,它很快就受到了欢迎。为了执行神经网络所需要的大量计算,这个参考实现依赖于一个计算后端。目前,你可以从三个流行的开源深度学习库中进行选:TensorFlow、Microsoft Cognitive Toolkit (CNTK)和Theano。因此,为了避免混淆,我们将这个参考实现称为multibackend Keras。
自2016年底以来,其他实现已经发布。你现在可以在Apache MXNet、苹果的Core ML、JavaScript或TypeScript(在web浏览器中运行Keras代码)和PlaidML(它可以在各种GPU设备上运行,不仅仅是Nvidia)上运行)上运行Keras。此外,TensorFlow本身现在与它自己的Keras实现绑定在一起,tf.keras。它只支持TensorFlow作为后端,但是它的优势在于提供了一些非常有用的额外特性:例如,它支持TensorFlow的Data API,这使得它可以很容易地高效地加载和预处理数据。因此,在这本书里,我们将使用tf.keras。然而,在本章中,我们不会使用任何TensorFlow特定的特性,因此代码在其他Keras实现上也能很好地运行(至少在Python中是这样),只需要做一些微小的修改,比如更改导入。
除了Keras和TensorFlow之外,最受欢迎的深度学习库是Facebook的PyTorch。好消息是,它的API与Keras非常相似(部分原因是这两个API都是受到Scikit-Learn和Chainer的启发),所以一旦你了解了Keras,如果你想切换到PyTorch并不难。PyTorch的人气在2018年呈指数级增长,这在很大程度上要归功于它的简单性和出色的文档,而这并不是TensorFlow1.x的主要优势。然而,TensorFlow2可以说和PyTorch一样简单,因为它采用了Keras作为它的官方高级API,并且它的开发人员极大地简化和清理了API的其余部分。文档也被完全重新组织,现在更容易找到你需要的内容。类似地,PyTorch的主要弱点(例如,有限的可移植性和没有计算图分析)在PyTorch 1.0中得到了很大的解决。像这样健康的竞争,对每个人都有利。
# 导入TensorFlow和tf。keras
import tensorflow as tf
from tensorflow import keras
首先,我们需要加载一个数据集。这里我们将使用Fashion MNIST,该数据集的格式为70000×28像素的灰度图像,共分10类。这些图片代表的是时尚物品,每一种分类图片多样,分类起来并不容易。
Keras提供了一些实用函数来获取和加载常见的数据集,包括MNIST、Fashion MNIST以及California housing等数据集。现在让我们载入Fashion MNIST:
# 加载数据集
fashion_mnist = keras.datasets.fashion_mnist
(X_train_full, y_train_full), (X_test, y_test) = fashion_mnist.load_data()
当使用Keras而不是Scikiti - learn加载MNIST或Fashion MNIST时,一个重要的区别是每张图片都是28×28的数组,而不是大小为784的一维数组。此外,像素强度被表示为整数(从0到255)而不是浮点数(从0.0到255.0)。让我们看看训练集的形状和数据类型:
# 训练集的形状
X_train_full.shape
(60000, 28, 28)
# 训练集的数据类型
X_train_full.dtype
dtype('uint8')
注意,数据集已经被分割成一个训练集和一个测试集,但是没有验证集,所以我们现在将创建一个验证集。此外,由于我们要使用梯度下降来训练神经网络,我们必须缩放输入特征。为了简单起见,我们将像素强度按比例除以255.0(这也将它们转换为浮点数),缩小到0-1范围:
# 创造验证集
X_valid, X_train = X_train_full[:5000] / 255., X_train_full[5000:] / 255.
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
X_test = X_test / 255.
对于Fashion MNIST,我们需要类名的列表来知道我们在处理什么,这里可以使用Matplotlib的imshow()函数,使用“二进制”颜色映射来绘制图像:
plt.imshow(X_train[0], cmap="binary")
plt.axis('off')
plt.show()
标签是类id(表示为uint8),取值从0到9;对应的类名为"T-shirt/top", "Trouser", "Pullover", "Dress", "Coat","Sandal", "Shirt","Sneaker", "Bag", "Ankle boot"
class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
"Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]
验证集包含5,000张图片,测试集包含10,000张图片。让我们看一看数据集中的图像样本:
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28]))
model.add(keras.layers.Dense(300, activation="relu"))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.Dense(10, activation="softmax"))
让我们逐行检查这段代码:
除了像我们刚才那样一个一个地添加网络层,你可以在创建顺序模型时传递一个层列表:
pyhton
keras.backend.clear_session()
np.random.seed(42)
tf.random.set_seed(42)
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dense(300, activation="relu"),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(10, activation="softmax")
])
model.summary()方法可以显示所有model的层,包括每个层的名称(它是自动生成的,除非你在创建层时设置它)、它的输出形状(None表示批大小可以是任何东西)和它的参数数量。Summary最后给出了参数的总数,包括可训练的和不可训练的参数。在这里,我们只有可训练参数(我们将在第11章中看到不可训练参数的例子)
model.summary()
请注意,密集的层通常有很多参数。例如,第一隐层有784×300的连接权值,加上300个偏置项,总计235,500个参数!这给了模型相当大的灵活性来拟合训练数据,但这也意味着模型有过拟合的风险,尤其是数据集不大时。
你可以很容易地获取一个模型的层列表,通过它的索引获取一个层,或者你可以通过名称获取它:
# 获得模型隐藏层的名字
hidden1 = model.layers[1]
hidden1.name
# 打印权重
# 获得隐藏层的权重和偏差
weights, biases = hidden1.get_weights()
weights
# 权重矩阵的形状
weights.shape
# 打印偏差
biases
# 打印偏差的形状
biases.shape
注意: 权重矩阵的形状取决于输入的数量。这就是为什么建议在顺序模型中创建第一层时指定input_shape的原因。但是,如果你不指定输入形状,那也没关系:Keras只会等待,直到它知道输入的形状,然后才真正地构建模型。当你向它提供实际数据时(例如,在训练期间),或者当你调用它的build()方法时,就会发生这种情况。在真正构建模型之前,这些层将没有任何权重,并且你将无法执行某些操作(例如打印模型摘要或保存模型)。因此,如果你在创建模型时知道输入形状,那么最好指定它。
在创建模型之后,你必须调用它的compile()方法来指定要使用的loss函数和优化器。可选地,你可以指定在训练和评估期间要计算的额外指标列表:
# 自定义模型指定要使用的loss函数和优化器
model.compile(loss="sparse_categorical_crossentropy",
optimizer="sgd",
metrics=["accuracy"])
这段代码需要一些解释。首先,我们使用“sparse_categorical_crossentropy”作为损失函数,因为我们有稀疏标签(即对于每个实例,只有一个目标类索引,在本例中是从0到9),并且类是互斥的。如果我们对每个实例每个类都有一个目标概率(比如一个one-hot向量,比如one-hot向量使用[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]来表示类3),那么我们需要使用“categorical_crossentropy”作为替代的损失函数。如果我们要进行二进制分类(使用一个或多个二进制标签),那么我们将在输出层中使用“sigmoid”激活函数,而不是“softmax”激活函数,并且我们将使用“binary_crossentropy”损失函数。
如果你想转换稀疏标签(比如类别标签)到one-hot向量标签时,你可以使用keras.util .to_categorical()函数。反过来,你需要使用axis=1的np.argmax()函数。
对于优化器,“sgd”意味着我们将使用简单的随机梯度下降来训练模型。换句话说,Keras将执行前面描述的反向传播算法(反向模式自动微分法加上梯度下降法)。
在使用SGD优化器时,调整学习率是很重要的。因此,你通常希望使用optimizer=keras. optimizs.sgd (lr=??)来设置学习率,而不是optimizer="sgd",而它的学习率默认值是lr=0.01。
最后,因为这个模型是一个分类器,所以在训练和评估时测量它的“准确性”是非常有用的。
现在我们已经准备好了模型,可以进行训练了。现在我们只需调用它的fit()方法:
# 训练模型
history = model.fit(X_train, y_train, epochs=30,
validation_data=(X_valid, y_valid))
我们向模型传递输入特征(X_train)和目标类(y_train),以及要训练的epoch数量(否则默认为1,dang'ran这肯定不足以收敛到一个好的解决方案)。我们还会向模型传递一个验证集(这是可选的)。Keras将在每个epoch结束时,会在这个集合上测量损失和额外的指标,这样地做法对于查看模型的实际表现是非常有用的。如果模型,训练集上的性能表现比在验证集上好得多,那么你的模型很可能是过度拟合了训练集(或者在模型中存在bug,比如训练集和验证集的数据不匹配)
在前面的内容中,神经网络已经被训练了。在训练中的每个epoch中,Keras显示实例的处理数量(以及一个进度条),还有每个样本在训练集和验证集上的平均训练时间和损失和准确性(或者是任何其他额外的指标要求)。你可以看到训练损失在下降,这是一个好的迹象,在30个epoch后,验证准确度达到89.26%。这和训练的准确度相差不大,所以似乎没有太多的过度拟合。
注意:不使用validation_data参数来传递验证集,你可以将validation_split设置为你希望Keras用于验证的训练集的比率。例如,validation_split=0.1告诉Keras使用数据的最后10%(在打乱之前)来进行验证。
如果训练集非常扭曲,有些类有过多的实例,但是其他的类实例太少,当调用fit()方法时设置class_weight参数将是非常有用的,这将给实例较少的的类一个更大的权重和实例过多的类一个较低的权重。这些权重将被Keras在计算损失时使用。
如果需要为每个实例设置权重,请设置sample_weight参数(这个参数将取代class_weight)。如果一些实例是由专家标记的,而其他实例是通过众包平台标记的,那么Per-instance权重可能会有用:因为你可能想要给前者更多的权重。你还可以为验证集提供sample weights(但不是类权重),通过将它们作为validation_data元组中的第三项添加。
fit()方法返回一个包含训练参数的历史对象(History .params):
import pandas as pd
pd.DataFrame(history.history).plot(figsize=(8, 5))
plt.grid(True)
plt.gca().set_ylim(0, 1)
plt.show()
可以看到,在训练过程中,训练精度和验证精度都在稳步上升,而训练损失和验证损失却在下降。这是一个好现象!此外,验证曲线与训练曲线比较接近,说明过拟合程度不高。在这个特殊的例子中,在训练开始时,模型在验证集上的表现似乎比在训练集上的表现更好。但事实并非如此:事实上,验证错误是在每个epoch结束时计算的,而训练错误是在每个epoch期间使用运行平均值计算的。所以训练曲线应该向左平移半个epoch。如果这样做,你会看到训练和验证曲线在训练开始时几乎完美地重叠。
接下来,我们可以使用模型的predict()方法对新实例进行预测。因为我们没有事实上的的新实例,所以我们只使用测试集的前三个实例:
X_new = X_test[:3]
y_proba = model.predict(X_new)
y_proba.round(2)
array([[0. , 0. , 0. , 0. , 0. , 0.01, 0. , 0.01, 0. , 0.98], [0. , 0. , 1. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ], [0. , 1. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]], dtype=float32)
如结果所示,对于每个实例,模型对每个类(从类0到类9)估计一个概率。例如,对于第一张图片,模型估计9类(踝靴)的概率为98%,5类(凉鞋)的概率为1%,7类(运动鞋)的概率为1%,而其他类别的概率可以忽略不计。换句话说,模型认为第一张图片是鞋子,最有可能是短靴,但也有可能是凉鞋或运动鞋。如果你只关心估计概率最高的类(即使该概率非常低),那么你可以可以使用predict_classes()方法作为代替:
y_pred = model.predict_classes(X_new)
y_pred
np.array(class_names)[y_pred]
array([9, 2, 1], dtype=int64)
在这里,分类器对这三幅图像进行了正确的分类
y_new = y_test[:3]
y_new
# Python ≥3.5
import sys
assert sys.version_info >= (3, 5)
# Scikit-Learn ≥0.20
import sklearn
assert sklearn.__version__ >= "0.20"
try:
%tensorflow_version 2.x
except Exception:
pass
# TensorFlow ≥2.0
import tensorflow as tf
assert tf.__version__ >= "2.0"
# 常规导入
import numpy as np
import os
# 使这个笔记本的输出在运行中保持稳定
np.random.seed(42)
# 图像的绘制设置
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)
import warnings
warnings.filterwarnings(action="ignore", message="^internal gelsd")
# 导入TensorFlow和tf。keras
import tensorflow as tf
from tensorflow import keras
# 加载数据集
fashion_mnist = keras.datasets.fashion_mnist
(X_train_full, y_train_full), (X_test, y_test) = fashion_mnist.load_data()
# 训练集的形状
X_train_full.shape
(60000, 28, 28)
# 训练集的数据类型
X_train_full.dtype
dtype('uint8')
# 创造验证集
X_valid, X_train = X_train_full[:5000] / 255., X_train_full[5000:] / 255.
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
X_test = X_test / 255.
plt.imshow(X_train[0], cmap="binary")
plt.axis('off')
plt.show()
class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
"Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]
n_rows = 4
n_cols = 10
plt.figure(figsize=(n_cols * 1.2, n_rows * 1.2))
for row in range(n_rows):
for col in range(n_cols):
index = n_cols * row + col
plt.subplot(n_rows, n_cols, index + 1)
plt.imshow(X_train[index], cmap="binary", interpolation="nearest")
plt.axis('off')
plt.title(class_names[y_train[index]], fontsize=12)
plt.subplots_adjust(wspace=0.2, hspace=0.5)
plt.savefig('images/pre4303.png', tight_layout=False)
plt.show()
<ipython-input-10-da3880afc3ff>:12: MatplotlibDeprecationWarning: savefig() got unexpected keyword argument "tight_layout" which is no longer supported as of 3.3 and will become an error two minor releases later plt.savefig('images/pre4303.png', tight_layout=False)
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28]))
model.add(keras.layers.Dense(300, activation="relu"))
model.add(keras.layers.Dense(100, activation="relu"))
model.add(keras.layers.Dense(10, activation="softmax"))
keras.backend.clear_session()
np.random.seed(42)
tf.random.set_seed(42)
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dense(300, activation="relu"),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(10, activation="softmax")
])
# 展示模型的层
model.layers
[<tensorflow.python.keras.layers.core.Flatten at 0x208f548eee0>, <tensorflow.python.keras.layers.core.Dense at 0x208f5375430>, <tensorflow.python.keras.layers.core.Dense at 0x208f5375820>, <tensorflow.python.keras.layers.core.Dense at 0x208f5375b80>]
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= flatten (Flatten) (None, 784) 0 _________________________________________________________________ dense (Dense) (None, 300) 235500 _________________________________________________________________ dense_1 (Dense) (None, 100) 30100 _________________________________________________________________ dense_2 (Dense) (None, 10) 1010 ================================================================= Total params: 266,610 Trainable params: 266,610 Non-trainable params: 0 _________________________________________________________________
# 获得模型隐藏层的名字
hidden1 = model.layers[1]
hidden1.name
# 打印权重
# 获得隐藏层的权重和偏差
weights, biases = hidden1.get_weights()
weights
# 权重矩阵的形状
weights.shape
# 打印偏差
biases
# 打印偏差的形状
biases.shape
(300,)
# 自定义模型指定要使用的loss函数和优化器
model.compile(loss="sparse_categorical_crossentropy",
optimizer="sgd",
metrics=["accuracy"])
# 训练模型
history = model.fit(X_train, y_train, epochs=30,
validation_data=(X_valid, y_valid))
Epoch 1/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.7237 - accuracy: 0.7643 - val_loss: 0.5213 - val_accuracy: 0.8226 Epoch 2/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.4842 - accuracy: 0.8316 - val_loss: 0.4351 - val_accuracy: 0.8514 Epoch 3/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.4391 - accuracy: 0.8455 - val_loss: 0.5269 - val_accuracy: 0.8008 Epoch 4/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.4123 - accuracy: 0.8567 - val_loss: 0.3915 - val_accuracy: 0.8648 Epoch 5/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.3939 - accuracy: 0.8620 - val_loss: 0.3746 - val_accuracy: 0.8694 Epoch 6/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.3750 - accuracy: 0.8675 - val_loss: 0.3710 - val_accuracy: 0.8734 Epoch 7/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.3630 - accuracy: 0.8713 - val_loss: 0.3617 - val_accuracy: 0.8726 Epoch 8/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.3515 - accuracy: 0.8752 - val_loss: 0.3858 - val_accuracy: 0.8620 Epoch 9/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.3412 - accuracy: 0.8789 - val_loss: 0.3582 - val_accuracy: 0.8720 Epoch 10/30 1719/1719 [==============================] - 3s 2ms/step - loss: 0.3319 - accuracy: 0.8821 - val_loss: 0.3419 - val_accuracy: 0.8784 Epoch 11/30 1719/1719 [==============================] - 3s 2ms/step - loss: 0.3238 - accuracy: 0.8839 - val_loss: 0.3453 - val_accuracy: 0.8786 Epoch 12/30 1719/1719 [==============================] - 3s 2ms/step - loss: 0.3147 - accuracy: 0.8864 - val_loss: 0.3304 - val_accuracy: 0.8822 Epoch 13/30 1719/1719 [==============================] - 3s 2ms/step - loss: 0.3077 - accuracy: 0.8898 - val_loss: 0.3276 - val_accuracy: 0.8870 Epoch 14/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.3018 - accuracy: 0.8918 - val_loss: 0.3397 - val_accuracy: 0.8790 Epoch 15/30 1719/1719 [==============================] - 3s 1ms/step - loss: 0.2943 - accuracy: 0.8938 - val_loss: 0.3219 - val_accuracy: 0.8852 Epoch 16/30 1719/1719 [==============================] - 3s 2ms/step - loss: 0.2887 - accuracy: 0.8972 - val_loss: 0.3097 - val_accuracy: 0.8904 Epoch 17/30 1719/1719 [==============================] - 3s 2ms/step - loss: 0.2835 - accuracy: 0.8979 - val_loss: 0.3568 - val_accuracy: 0.8728 Epoch 18/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2774 - accuracy: 0.9005 - val_loss: 0.3137 - val_accuracy: 0.8916 Epoch 19/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2724 - accuracy: 0.9023 - val_loss: 0.3112 - val_accuracy: 0.8916 Epoch 20/30 1719/1719 [==============================] - 3s 1ms/step - loss: 0.2670 - accuracy: 0.9037 - val_loss: 0.3260 - val_accuracy: 0.8808 Epoch 21/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2621 - accuracy: 0.9058 - val_loss: 0.3048 - val_accuracy: 0.8940 Epoch 22/30 1719/1719 [==============================] - 3s 1ms/step - loss: 0.2575 - accuracy: 0.9076 - val_loss: 0.2961 - val_accuracy: 0.8982 Epoch 23/30 1719/1719 [==============================] - 3s 1ms/step - loss: 0.2533 - accuracy: 0.9081 - val_loss: 0.2982 - val_accuracy: 0.8930 Epoch 24/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2481 - accuracy: 0.9105 - val_loss: 0.3066 - val_accuracy: 0.8896 Epoch 25/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2439 - accuracy: 0.9130 - val_loss: 0.2977 - val_accuracy: 0.8950 Epoch 26/30 1719/1719 [==============================] - 3s 2ms/step - loss: 0.2402 - accuracy: 0.9133 - val_loss: 0.3059 - val_accuracy: 0.8892 Epoch 27/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2360 - accuracy: 0.9155 - val_loss: 0.3018 - val_accuracy: 0.8952 Epoch 28/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2324 - accuracy: 0.9163 - val_loss: 0.2990 - val_accuracy: 0.8924 Epoch 29/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2281 - accuracy: 0.9186 - val_loss: 0.3029 - val_accuracy: 0.8934 Epoch 30/30 1719/1719 [==============================] - 2s 1ms/step - loss: 0.2245 - accuracy: 0.9198 - val_loss: 0.3029 - val_accuracy: 0.8932
import pandas as pd
pd.DataFrame(history.history).plot(figsize=(8, 5))
plt.grid(True)
plt.gca().set_ylim(0, 1)
plt.savefig("images/pre4307.png")
plt.show()
X_new = X_test[:3]
y_proba = model.predict(X_new)
y_proba.round(2)
array([[0. , 0. , 0. , 0. , 0. , 0.01, 0. , 0.02, 0. , 0.96], [0. , 0. , 0.98, 0. , 0.02, 0. , 0. , 0. , 0. , 0. ], [0. , 1. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]], dtype=float32)
y_pred = model.predict_classes(X_new)
y_pred
WARNING:tensorflow:From <ipython-input-21-81ace37e545f>:1: Sequential.predict_classes (from tensorflow.python.keras.engine.sequential) is deprecated and will be removed after 2021-01-01. Instructions for updating: Please use instead:* `np.argmax(model.predict(x), axis=-1)`, if your model does multi-class classification (e.g. if it uses a `softmax` last-layer activation).* `(model.predict(x) > 0.5).astype("int32")`, if your model does binary classification (e.g. if it uses a `sigmoid` last-layer activation).
array([9, 2, 1], dtype=int64)
plt.figure(figsize=(7.2, 2.4))
for index, image in enumerate(X_new):
plt.subplot(1, 3, index + 1)
plt.imshow(image, cmap="binary", interpolation="nearest")
plt.axis('off')
plt.title(class_names[y_test[index]], fontsize=12)
plt.savefig('images/pre4308.png', tight_layout=False)
plt.show()
<ipython-input-22-f5c0bf10a93b>:7: MatplotlibDeprecationWarning: savefig() got unexpected keyword argument "tight_layout" which is no longer supported as of 3.3 and will become an error two minor releases later plt.savefig('images/pre4308.png', tight_layout=False)